# -*- coding: utf-8 -*-

import os
import sys

if __name__ == "__main__":
    currentdir = os.path.dirname( os.path.abspath( sys.argv[0] ) )
    basedir = os.path.abspath( os.path.join( currentdir, ".." ) )
    pythondir = basedir
    sys.path.insert( 0, pythondir )
    pass

from logger import get_file_logger
import traceback
import mimetypes
from time import time
from picklerpc.msghandler import RequestHandler
from picklerpc.sendmsg import sendmsg
from picklerpc import Server
from time import sleep
from datetime import timedelta
from service import default_request_timeout, default_niceness_level, Py27ServiceManager as ServiceManager
from multiprocessing import cpu_count

__all__ = ['ImageVectorHandler', 'ImageVectorServer', 'ImageVectorRequest', 'ImageVectorManager']

#---------------------------------------------

class ImageVectorHandler(RequestHandler):
    """
    Do some tasks, maybe in subprocess
    """
    
    def _request_handle(self, data):
        """
        @param data: simple object data
        @type: dict
        
        @return: python simple object data, jsonable via json.dumps
        @rtype: mixed
        """
        result = False
        
        if not (type(data) is dict):
            return result

        if data["task"] == "ping":
            return self.server.ping()
        if data["task"] == "quit":
            return self.server.quit()
        if data["task"] == "reset-stats":
            return self.server.reset_stats()
        if data["task"] == "get-stats":
            return self.server.get_stats()
        if data["task"] == "process":
            data = data["data"]
            return self.server.process(
                data["file_id"],
                data["image_path"],
                data["tmp_dir"],
                data["inc_size"]
            )
        return result
    
    pass

#---------------------------------------------

class ImageVectorServer(Server):
    
    queue = None
    stop_event = None
    task_service_address = None
    _worker_thread = None

    count_items = 0
    count_processed = 0

    vector_maxsize = 100

    affinity = -1

    stats_req = None
    db_req = None

    _lock = None

    log = None

    def server_activate(self):
        try:
            # Python 3
            from queue import Queue
        except ImportError:
            # Python 2
            from Queue import Queue

        from threading import Thread, Event, Lock

        self.log = get_file_logger("ImageVectorServer")

        if self.affinity > -1:
            try:

                from libc import set_affinity

                set_affinity(0, 2**(self.affinity % cpu_count()))

            except Exception as e:
                self.log.error(e)
                self.log.error(traceback.format_exc())

        self._lock = Lock()

        self.queue = Queue()
        self.stop_event = Event()
        self._worker_thread = Thread(target=self._worker)
        self._worker_thread.start()

        return Server.server_activate(self)


    def ping(self):
        return "pong"


    def get_mime_type( self, full_path ):
        mime = mimetypes.guess_type( full_path )[0]
        if not mime:
            mime = "application/octet-stream"
        return mime

    def run_image(self, img, file_id, image_path):
        """
        @param img;
        @type img: images.pil.iPIL
        """

        self.stats_req.inc("images_count", 1)

        try:
            fsize = os.path.getsize( image_path )
            mime = self.get_mime_type( image_path )

            self.stats_req.inc("images_size", fsize )

            img.load(image_path)

            w = img.width()
            h = img.height()

            vector_opencv = img.imageToOpenCvDescriptors()

            color = img.isColour()
            vector_gray = img.imageToGrayStr()
            vector = img.imageToBitStr()
            bitmask = img.imageToInt64()

            img_id = self.db_req.insert_image(
                file_id, mime, fsize, color,
                w, h
            )

            """
            v0, v1, v2, v3, v4, v5, v6, v7, v8, v9 = res["vector"]
            self.db_req.insert_image_v10( file_id,
                img_id, IMAGE_VECTOR_ORIENTATION_NORMAL ,
                v0, v1, v2, v3, v4, v5, v6, v7, v8, v9
                )

            img.load(image_path)

            res = img.process_image(parts=self.vector_maxsize)

            self.db_req.insert_image_vector_big(
                file_id, img_id, IMAGE_VECTOR_ORIENTATION_NORMAL,
                res["vector"]
            )
            """

            self.db_req.insert_image_vector_bitstr(
                file_id, img_id, bitmask, vector
            )

            self.db_req.insert_image_vector_gray(
                file_id, img_id, vector_gray
            )

            self.db_req.insert_image_vector_numpy(
                file_id, img_id, vector_opencv
            )

            self.stats_req.inc("images_processed", 1)

        except Exception as e:
            self.stats_req.inc("errors_count", 1)
            self.log.error("::run_image(): File=%r, %s" % (
                image_path, e, ) )
            self.log.debug("::run_image():\nFile=%r\n ERROR!\n%s" % (
                image_path, traceback.format_exc() ) )
            pass

        return

    def _worker ( self ):

        self.log.info("START: %r" % (self.server_address,))

        from service.task import TaskManager
        from images.pil import iPIL as iMage
        from service.statistics import StatisticsDirectRequest
        from project.query import Project

        mimetypes.init()

        task_manager = TaskManager(self.task_service_address)

        self.stats_req = task_manager.get_request().getStatistics()
        while not self.stats_req.ping():
            sleep(0.1)
        addr = self.stats_req.get_address()
        self.stats_req = StatisticsDirectRequest(addr)


        self.db_req = Project()

        img = iMage()

        while not self.stop_event.isSet():

            try:
                data = self.queue.get_nowait()
            except Exception:
                data = None

            if data is None:
                sleep(0.1)
                continue


            file_id, image_path, tmp_dir, inc_size = data

            try:
                if img.is_image(image_path):
                    self.run_image(img, file_id, image_path )

                self.stats_req.inc_imaging(1)
            except Exception as e:
                self.stats_req.inc("errors_count", 1)
                self.log.error("::_worker: %s" % (e,))
                self.log.debug("ERROR!\n%s" % (traceback.format_exc(),))
                pass

            self.stats_req.inc("archives_processed_size", inc_size)

            self._lock.acquire()
            self.count_processed += 1
            self._lock.release()

            self.queue.task_done()


        self.log.info("STOP: %r" % (self.server_address,))

        self.stop_event.clear()
        return


    def process(self, file_id, image_path, tmp_dir, inc_size):
        self._lock.acquire()
        self.count_items += 1
        self._lock.release()

        self.queue.put_nowait((file_id, image_path, tmp_dir, inc_size,))
        return True

    def reset_stats(self):
        self._lock.acquire()
        self.count_items = 0
        self.count_processed = 0
        self._lock.release()
        return True

    def get_stats(self):
        self._lock.acquire()
        item = {
            "items" : self.count_items,
            "processed" : self.count_processed
            }
        self._lock.release()
        return item

    def quit(self):
        self.log.info("quit event")

        import signal

        self.log.debug("send SIGINT to self")
        os.kill( os.getpid(), signal.SIGINT )

        return True


    def terminate_self(self, signum, frame):
        self.log.debug("got SHUTDOWN signal")
        t_begin = time()
        self.stop_event.set()
        self.stop_event.wait()
        self.server_close()
        t_end = time()
        self.log.info("SHUTDOWN in %s sec" % (timedelta(seconds=t_end - t_begin),))
        return

    pass


#---------------------------------------------

class ImageVectorRequest(object):

    manager = None

    def __init__(self, manager):
        self.manager = manager
        pass

    def process(self, number, file_id, image_path, tmp_dir, inc_size):
        return self.manager.sendmsg( number, "process", {
            "file_id" : file_id,
            "image_path" : image_path,
            "tmp_dir" : tmp_dir,
            "inc_size" : inc_size
        })

    def reset_stats(self, number):
        return self.manager.sendmsg( number, "reset-stats", {})

    def get_stats(self, number):
        return self.manager.sendmsg( number, "get-stats", {})

    def ping(self, number):
        return self.manager.sendmsg( number, "ping", {}, 0.1)

    def ping_all(self):
        return self.manager.ping_all()

    def quit(self):
        return self.manager.quit_all()

    pass

#---------------------------------------------

class ImageVectorManager(ServiceManager):

    host = "127.0.1.10"
    port_min = 10000
    port_max = 20000
    ports = None
    request = None

    task_service_address = None

    log = None

    def __init__(self, task_service_address):
        self.task_service_address = task_service_address
        self.ports = {}
        self.log = get_file_logger("ImageVectorManager")
        pass

    def start_service(self, number):

        number = str(number)

        runing = False
        if number not in self.ports:

            import random

            port = random.randint(self.port_min, self.port_max)

            self.ports[number] = port

            address = (self.host, port,)

            from subprocess import Popen

            Popen([self.getPython(), "service/image_vector.py",
                   "--host", "%s" % self.host,
                   "--port", "%s" % port,
                   "--task-service", "%s:%s" % self.task_service_address
            ])

            # WAIT WHILE SERVICE STARTS

            pings = 100
            t_begin = time()
            while not runing:

                pong = self.get_request().ping(number)

                if pong == "pong":
                    runing = True

                pings -= 1
                if pings <= 0:
                    break

            if pings <= 0:
                del self.ports[number]
                self.log.error("FAIL TO START service(%s) on %r in %s sec" % (
                    number,
                    address, timedelta(seconds=time() - t_begin),))
            else:
                self.log.info("STARTED service(%s) on %r in %s sec" % (
                    number,
                    address, timedelta(seconds=time() - t_begin),))

        return runing

    def sendmsg(self, number, task, data, timeout=default_request_timeout):
        number = str(number)
        port = self.ports[number]
        address = (self.host, port,)
        try:
            result = sendmsg( address, {"task":task, "data":data}, timeout=timeout )
        except Exception as e:
            result = None
            self.log.error("SOCKET: %s" % ( e,))
            self.log.debug("FAIL SEND MESSAGE: task=%s, data=%r" % (
                task, data,))
        return result

    def quit_all(self):
        result = True
        ports = self.ports.copy()
        for number, port in ports.items():
            ret = self.sendmsg(number, "quit", {}, 0.1)
            if ret is None:
                result = False
            else:
                del self.ports[ number ]
        return result

    def ping_all(self):
        result = False
        ports = self.ports.copy()
        for number, port in ports.items():
            ret = self.get_request().ping(number)
            if ret == "pong":
                result = True
        return result

    def get_request(self):
        if not self.request:
            self.request = ImageVectorRequest(self)
        return self.request

    pass


#---------------------------------------------

def run_service(host, port, task_service_address, affinity=-1):

    if hasattr(os, "nice"):
        os.nice(default_niceness_level)

    server = None
    e_code = 0
    try:
        SERVER_ADDRESS = (host, int(port),)

        server = ImageVectorServer(SERVER_ADDRESS, ImageVectorHandler, bind_and_activate=False)
        if task_service_address:
            host, port = task_service_address.split(':')
            server.task_service_address = (host, int(port),)

        server.affinity = affinity

        import signal

        signal.signal(signal.SIGINT, server.terminate_self)

        server.server_bind()
        server.server_activate()
        server.serve_forever()
    except Exception as e:
        e_code = 1
        if server and server.log:
            server.log.error("run_service: ERROR=%r" % (e,))
        pass

    return e_code


#---------------------------------------------

def launch():

    from argparse import ArgumentParser

    parser = ArgumentParser(description='Run image vector processing service.')
    parser.add_argument('--host', dest='host', action='store', type=str,
        help='Listen on TCP address')
    parser.add_argument('--port', dest='port', action='store', type=int,
        help='Listen on TCP port')
    parser.add_argument('--task-service', dest='task_service', action='store', type=str,
        help='Task service listen on TCP address')
    parser.add_argument('--affinity', dest='affinity', action='store', type=int,
                        help='Link to CPU, defaults=-1 - auto', default=-1)

    args = parser.parse_args()
    return run_service(args.host, args.port, args.task_service, args.affinity)

if __name__ == "__main__":
    sys.exit(launch())
